package com.ruoyi.web.controller.shortVedio;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;

/**
 * 下载代理控制器
 * 前端传入原始视频直链，后端拉取并将二进制内容回传，避免小程序前端直连被拦截
 */
@Api(tags = "短视频下载")
@RestController
@RequestMapping("/parse")
@CrossOrigin(origins = "*")
public class DownController {

    private static final Logger log = LoggerFactory.getLogger(DownController.class);

    @ApiOperation(value = "短视频下载", notes = "下载链接")
    @ApiImplicitParam(name = "url", value = "下载链接", required = true, dataType = "string", paramType = "query")
    @GetMapping(value = "/down")
    public ResponseEntity<byte[]> DownVideo(@RequestParam("url") String url,
                                            @RequestParam(value = "filename", required = false) String filename) {
        try {
            String decoded = URLDecoder.decode(url, StandardCharsets.UTF_8.name());
            FetchResult fetched = fetchBinaryWithRedirect(decoded, 5);

            HttpHeaders headers = new HttpHeaders();
            // 优先使用源站的 Content-Type，如果不可用则按 mp4 处理，避免 .bin
            MediaType ct = MediaType.APPLICATION_OCTET_STREAM;
            if (StringUtils.hasText(fetched.contentType)) {
                try {
                    ct = MediaType.parseMediaType(fetched.contentType);
                } catch (Exception ignore) {}
            }
            if (!ct.getType().equalsIgnoreCase("video")) {
                // 对不明确类型强制声明为 mp4，提升端侧识别率
                ct = MediaType.valueOf("video/mp4");
            }
            headers.setContentType(ct);
            headers.set(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
            headers.set(HttpHeaders.PRAGMA, "no-cache");
            headers.set(HttpHeaders.EXPIRES, "0");
            if (fetched.contentLength > 0) {
                headers.setContentLength(fetched.contentLength);
            }

            String fileName = StringUtils.hasText(filename) ? sanitizeFilename(filename) : deriveFileName(decoded);
            if (!fileName.toLowerCase().endsWith(".mp4")) {
                fileName = fileName + ".mp4";
            }
            headers.set(HttpHeaders.CONTENT_DISPOSITION, buildContentDisposition(fileName));

            return new ResponseEntity<>(fetched.data, headers, HttpStatus.OK);
        } catch (Exception e) {
            log.error("下载代理失败: {}", e.getMessage(), e);
            return ResponseEntity.status(HttpStatus.BAD_GATEWAY)
                    .contentType(MediaType.APPLICATION_JSON)
                    .body(("{\"code\":502,\"msg\":\"download failed\"}").getBytes(StandardCharsets.UTF_8));
        }
    }

    private static FetchResult fetchBinaryWithRedirect(String url, int maxRedirects) throws Exception {
        String current = url;
        int attempts = 0;
        while (attempts <= maxRedirects) {
            HttpURLConnection conn = (HttpURLConnection) new URL(current).openConnection();
            conn.setInstanceFollowRedirects(false);
            conn.setConnectTimeout(15000);
            conn.setReadTimeout(60000);
            // 基础请求头（最小集）
            conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36");
            conn.setRequestProperty("Accept", "*/*");
            conn.connect();

            int status = conn.getResponseCode();
            if (status == HttpURLConnection.HTTP_MOVED_PERM || status == HttpURLConnection.HTTP_MOVED_TEMP || status == 307 || status == 308) {
                String location = conn.getHeaderField("Location");
                if (!StringUtils.hasText(location)) break;
                current = location;
                attempts++;
                continue;
            }

            try (InputStream is = conn.getInputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
                byte[] buf = new byte[8192];
                int len;
                while ((len = is.read(buf)) != -1) {
                    bos.write(buf, 0, len);
                }
                FetchResult r = new FetchResult();
                r.data = bos.toByteArray();
                r.contentType = conn.getContentType();
                try {
                    r.contentLength = conn.getContentLengthLong();
                } catch (Throwable ignore) { r.contentLength = -1; }
                return r;
            }
        }
        throw new IllegalStateException("Too many redirects or invalid URL");
    }

    private static String deriveFileName(String url) {
        try {
            String path = new URL(url).getPath();
            String name = path.substring(path.lastIndexOf('/') + 1);
            if (!StringUtils.hasText(name)) return "video.mp4";
            if (!name.contains(".")) name += ".mp4";
            return name;
        } catch (Exception e) {
            return "video.mp4";
        }
    }

    private static String sanitizeFilename(String name) {
        // 去除非法文件名字符并裁剪长度
        String safe = name.replaceAll("[\\\\/:*?\"<>|]", "_").trim();
        if (safe.length() > 80) {
            safe = safe.substring(0, 80);
        }
        if (!StringUtils.hasText(safe)) {
            return "video";
        }
        return safe;
    }

    private static String buildContentDisposition(String filename) {
        String asciiFallback = filename.chars().allMatch(c -> c >= 32 && c <= 126)
                ? filename
                : filename.replaceAll("[^\\x20-\\x7E]", "_");
        String encoded;
        try {
            encoded = java.net.URLEncoder.encode(filename, "UTF-8").replaceAll("\\+", "%20");
        } catch (Exception e) {
            encoded = filename;
        }
        // 同时设置 filename 和 filename* 以兼容非 ASCII 文件名
        return "attachment; filename=\"" + asciiFallback + "\"; filename*=UTF-8''" + encoded;
    }

    private static class FetchResult {
        byte[] data;
        String contentType;
        long contentLength;
    }
}
